# /**
# * Copyright (c) 2022 Beijing Jiaotong University
# * PhotLab is licensed under [Open Source License].
# * You can use this software according to the terms and conditions of the [Open Source License].
# *
# * You may obtain a copy of [Open Source License] at: [https://open.source.license/]
# *
# * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# *
# * See the [Open Source License] for more details.
# */
# /**
# * Author: Shihao Xie
# * Created: Apr. 26, 2023
# * Supported by: National Key Research and Development Program of China
# */

import os
import time

from phot_server.server_config.global_config import LIBRARY_ROOT, PROJECT_ROOT, GRAPH_POSTFIX, GRID_POSTFIX, COMPONENT_POSTFIX, \
    CONFIG_POSTFIX, CONFIG_FILE_NAME, ICON_ROOT
from phot_server.core.exception import FileError, FilePathError
from phot_server.rpc.file_data_type.ttypes import PhotFile, PhotFileType
from phot_server.utils.phtc import parse_phtc
from phot_server.utils.phtg import parse_phtg
from phot_server.utils.minio_api import Bucket, is_minio_path, split_minio_path
from phot_server.server_config.minio_config import MINIO_CONF


def isdir(path):
    if is_minio_path(path):
        bucket, name = split_minio_path(path)
        client = Bucket(**MINIO_CONF)
        file = client.stat_object(bucket, name)
        return file.is_dir
    return os.path.isdir(path)


def isfile(path):
    if is_minio_path(path):
        bucket, name = split_minio_path(path)
        client = Bucket(**MINIO_CONF)
        file = client.stat_object(bucket, name)
        return file.is_dir is False
    return os.path.isfile(path)


def exists(path):
    if is_minio_path(path):
        bucket, name = split_minio_path(path)
        client = Bucket(**MINIO_CONF)
        file_list = client.bucket_list_files(bucket, name)
        return len(file_list) > 0
    return os.path.exists(path)


def read_file_attribute(absolute_path):
    """
    This function is to read file object attribute in given path
    :param absolute_path: the path of the file object to be got attribute
    :return:
    """
    file = PhotFile()

    # if is_minio_path(absolute_path):
    #     client = Bucket(**MINIO_CONF)
    #     bucket, name = split_minio_path(absolute_path)
    #     file_state = client.stat_object(bucket, name)
    #     file.name = file_state.object_name
    #     file.fileSize = file_state.size
    #     file.lastModificationTime = file_state.last_modified
    #     if file_state.is_dir:
    #         if
    os.chmod(absolute_path, 0o777)
    file_name = os.path.basename(absolute_path)
    # Put file and directory information to PhotFile
    file.path = absolute_path
    file.name = file_name
    if isdir(absolute_path):
        file.type = is_project(absolute_path)
    elif isfile(absolute_path):
        file.type = get_file_type(absolute_path)
    else:
        raise FilePathError("The path '{}' has error.".format(absolute_path))
    if file.type == PhotFileType.GRAPH:
        (graph, render_graph) = parse_phtg(absolute_path)
        file.description = graph.description
    elif file.type == PhotFileType.COMPONENT:
        component = parse_phtc(absolute_path)
        file.description = component.description
        if component.iconName is not None and component.iconName != "":
            file.icon = open_file(os.path.join(ICON_ROOT, component.iconName)).data
    file_info = os.stat(absolute_path)
    file.fileSize = file_info.st_size
    file.lastAccessTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(file_info.st_atime))
    file.lastModificationTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(file_info.st_mtime))
    file.createTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(file_info.st_ctime))

    return file


def open_directory(absolute_path: str) -> list:
    """
    This function is to access the directories and its files in given path
    :param absolute_path: the absolute path of destination folder
    :return: files and directories list in path
    """
    # Turn '/' or '\\' to system os.sep, which is '\\' in windows and '/' in linux
    directory_path = convert_seperator(absolute_path)

    if isdir(absolute_path) is False:
        raise FileNotFoundError(
            "The file object in path '{}' is not a directory or is not exist.".format(absolute_path))

    # Get file and directory objects in target path
    file_objects = os.listdir(directory_path)
    os.chmod(directory_path, 0o777)
    object_list = []
    # traverse all file
    for object_name in file_objects:
        file_path = directory_path + os.sep + object_name
        file = read_file_attribute(file_path)
        # Add to list
        object_list.append(file)
    return object_list


def open_file(absolute_path) -> PhotFile:
    """
    This function is to read a file in project or library
    :param absolute_path: the file path to be opened
    :return: the PhotFile object that contain target file data
    """
    file_path = convert_seperator(absolute_path)

    if isfile(path=file_path) is False:
        raise FileNotFoundError("The file object in path '{}' is not a file or is not exist.".format(absolute_path))

    # Put file and directory information to PhotFile
    file = read_file_attribute(file_path)

    if file.type != "DIRECTORY" and file.type != "PROJECT":
        # Put the file content to PhotFile.data
        file_stream = open(absolute_path, 'rb')
        file.data = file_stream.read()
        file_stream.close()
        return file
    else:
        raise FileError("The file Object in Path {} is a {}, it need to be a file.".format(file_path, file.type))


def new_directory(directory_path):
    """
    This method is to create a directory in target path
    :param directory_path: the parent path of new directory
    :return: the file objects in the parent path of this directory
    """
    directory_path = convert_seperator(directory_path)
    if not isdir(directory_path):
        os.makedirs(directory_path)
        os.chmod(directory_path, 0o777)
    else:
        raise FileError("Directory in '{}' had exist.".format(directory_path))


def new_file(absolute_path, file_object):
    """
    This method is to create a file
    :param absolute_path: the path of the file to create.
    :param file_object: PhotFile object which contain the file information
    :return: PhotFile object of the new file
    """
    if exists(absolute_path):
        raise FileError("There had been a file in path '{}'".format(absolute_path))
    file_stream = open(absolute_path, "wb")
    file_stream.write(file_object.data)
    file_stream.close()
    os.chmod(absolute_path, 0o777)


def delete(absolute_path):
    """
    This method is to delete a file or directory
    :param absolute_path: the file object path to be deleted
    :return: True of False. Means if had deleted the file or directory
    """
    delete_all(absolute_path)
    return exists(absolute_path) is False


def save(absolute_path, file_object):
    """
    This method is to delete a file or directory
    :param absolute_path: the path of file to save
    :param file_object: PhotFile object which contain the file information
    :return: PhotFile object of the saved file
    """
    if exists(absolute_path) and isfile(absolute_path):
        os.chmod(absolute_path, 0o777)
        file_stream = open(absolute_path, 'wb')
        file_stream.write(file_object.data)
        file_stream.close()
    else:
        raise FileNotFoundError("File in path '{}' is not found.".format(absolute_path))


def convert_seperator(path):
    """
    This method is to turn '/' or '\\' to the system default file path os.sep
    :param path: file path
    :return: the new path with system default os.sep
    """
    return path.replace("\\", os.sep).replace("/", os.sep)


def get_absolute_path(relative_path, root_type):
    """
    This method is to turn relative path to absolute path
    :param relative_path: the path relative the library or project root path
    :param root_type: the root type, including library and project
    :return: absolute path
    """
    if root_type.lower() == "project":
        return os.path.realpath(os.path.join(PROJECT_ROOT, convert_seperator(relative_path)))
    elif root_type.lower() == "library":
        return os.path.realpath(os.path.join(LIBRARY_ROOT, convert_seperator(relative_path)))
    else:
        raise Exception("Root Path type is either project or library")


def delete_all(absolute_path):
    """
    This method is to delete the file or directory recursively.
    :param absolute_path: the target path to be deleted
    :return: None
    """
    os.chmod(absolute_path, 0o777)
    if isfile(absolute_path):
        # Delete file
        os.remove(absolute_path)
    else:
        # Delete directory recursively
        for file_name in os.listdir(absolute_path):
            delete_all(absolute_path + os.sep + file_name)
        os.rmdir(absolute_path)


def get_file_list(absolute_path):
    pass


def is_project(absolute_path):
    if exists(absolute_path + os.sep + CONFIG_FILE_NAME):
        return PhotFileType.PROJECT
    else:
        return PhotFileType.DIRECTORY


def get_file_type(absolute_path):
    """
    This method is to judge the type of path
    :param absolute_path: the target path to be judged
    :return: object type including PROJECT,DIRECTORY,GRAPH,COMPONENT.
    """
    [file_name, suffix] = os.path.splitext(absolute_path)
    if suffix == GRAPH_POSTFIX:
        return PhotFileType.GRAPH
    if suffix == COMPONENT_POSTFIX:
        return PhotFileType.COMPONENT
    if suffix == GRID_POSTFIX:
        return PhotFileType.GRID
    if suffix == CONFIG_POSTFIX:
        return PhotFileType.CONFIG
    return PhotFileType.OTHER


def is_subdir(path, directory):
    path = os.path.realpath(path)
    directory = os.path.realpath(directory)

    relative = os.path.relpath(path, directory)

    if relative.startswith(os.pardir):
        return False
    else:
        return True
